import express from 'express'
import { createServer } from 'http'
import { join, extname, posix } from 'path'
import { readFileSync } from 'fs'
import chokidar from 'chokidar'
import WebSocket from 'ws'
import { transformCode, transformJSX, transformCss } from './transform'

const targetRootPath = join(__dirname, '../target')

function createWebSocketServer(server) {
  const wss = new WebSocket.Server({
    noServer: true
  })

  server.on('upgrade', (req, socket, head) => {
    if (req.headers['sec-websocket-portocol'] === 'vite-hmr') {
      wss.handleUpgrade(req, socket, head, ws => {
        wss.emit('connection', ws, req)
      })
    }
  })

  wss.on('connection', socket => {
    socket.send(JSON.stringify({ type: 'connected' }))
  })

  wss.on('error', (e) => {
    console.log('server site websocket error', e)
    if (e.code !== 'EADDRINUSE') {
      console.error(
        chalk.red(`WebSocket server error: \n${e.stack || e.message}`)
      )
    }
  })

  return {
    send(payload) {
      const stringified = JSON.stringify(payload)
      wss.clients.forEach(client => {
        if (client.readState === WebSocket.OPEN) {
          client.send(stringified)
        }
      })
    },
    close() {
      wss.close()
    }
  }
}

//监听文件变更
function watch() {
  return chokidar.watch(targetRootPath, {
    ignored: ['**/node_modules/**', '**/.cache/**'],
    ignoreInitial: true,
    ignorePermissionErrors: true,
    disableGlobbing: true
  })
}

function getShortName(file, root) {
  return file.startsWith(root + '/') ? posix.relative(root, file) : file
}

function handleHMRUpdata(opts) {
  const { file, ws } = opts
  const shortFile = getShortName(file, targetRootPath)
  const timestamp = Date.now()

  let updates
  if (shortFile.endsWith('.css') || shortFile.endsWith('.jsx')) {
    updates = [
      {
        type: 'js-update',
        timestamp,
        path: `/${shortFile}`,
        acceptPath: `${shortFile}`
      }
    ]
  }

  ws.send({
    type: 'update',
    updates
  })
}

// 1、 起一个服务，express,koa,golang
// 2、 拦截入口请求：localhost:3002 -》返回html文件
// 3、加一些骚操作，比如热更新HMR
export async function dev() {
  const app = express()
  app.get('/', (req, res) => {
    res.set('Content-Type', 'text/html')
    const htmlPath = join(__dirname, '../target', 'index.html')
    let html = readFileSync(htmlPath, 'utf-8')

    html = html.replace('<head>', `<head>\n <script type='module' src='/@vite/client'></script>`)
    res.send(html)
  })

  app.get('/@vite/client', (req, res) => {
    res.set('Content-Type', 'application/javascript')
    res.send(
      // 返回的才是真正的内置的客户端内容
      transformCode({
        code: readFileSync(join(__dirname, 'client.js'), 'utf-8')
      }).code
    )
  })

  app.get('/target/*', (req, res) => {
    console.log('path >>>', req.path)
    const fillPath = join(__dirname, '..', req.path.slice(1))
    if ('import' in req.query) {
      res.set('Content-Type', 'application/javascript')
      res.send(`export default "${req.path}"`)
      return;
    }

    switch (extname(req.path)) {
      case '.svg':
        res.set('Content-Type', 'image/svg+xml')
        res.send(
          readFileSync(fillPath, 'utf-8')
        )
        break
      case '.css':
        res.set('Content-Type', 'application/javascript')
        res.send(
          transformCss({
            path: req.path,
            code: readFileSync(fillPath, 'utf-8')
          })
        )
        break
      default:
        res.set('Content-Type', 'application/javascript')
        res.send(
          transformJSX({
            appRoot: join(__dirname, '../target'),
            path: req.path,
            code: readFileSync(fillPath, 'utf-8')
          }).code
        )
    }
  })

  const server = createServer(app)

  const ws = createWebSocketServer(server)

  watch().on('change', async ({ file }) => {
    handleHMRUpdata({ file, ws })
  })

  const port = 3002
  server.listen(port, () => {
    console.log('App is runing at  127.0.0.1:' + port)
  })
}
